Overview
When requesting an event from a store or a semaphore it is often necessary to request not just the next available read buffer but some specific read buffer. In order to achieve this, CLIP introduces the notion of keys and rules, which allow individual buffers to be tagged with a value that is interrogated when they are requested.
>
Data Store Static/Dynamic Keys
Normally store writer keys are statically set when the store is opened, however, keys are not actually applied until the store record is constructed. Thus a writer can open a store with the default key, and dynamically set a new key later when the store's buffer is constructed (requires an overloaded Construct() to be defined). This allows an active object to grab a write buffer immediately, but delay setting the key until it has made a separate decision based on some programmatic criteria.
Passive objects (event propagators) always open stores with static keys. If a dynamic key is required the passive objects should use the default key and the active object writers can then set the key dynamically when they construct the store buffer.
Active object store readers can apply dynamic keys if they make a direct auxiliary connection to the store. Any connections made via passive objects or trigger connections use static keys. In this case, readers can only 'filter' keys by opening the store with the default key and then checking what key was actually specified. If an invalid key is found the store can be closed/aborted. However, care must be used in deciding which to do (See Transient Stores - Aborting).
Element Numbers as KeysWhen specifying object element numbers for keys on connections (In the Properties window for a connection) use elems.ElemNum(). NB. this is the element number of the object doing the connecting/opening, not the element number of the object being connected to. Macros can also be used. However, when using <>Cxn.OpenRead/Write() functions in Methods/Threads use the ElemNum() function (or this->ElemNum() ). ExamplesPrioritisationKeys and rules can be used to implement a priority queue using a transient store. When writing into the store, a key is applied with its value set to the priority of the buffer. When reading out the MAX rule is used to read the buffer with the highest priority first. RoutingKeys can be used to route particular buffers to particular consumers. When writing to the store, the target 'address' is set as the buffer key. When reading out, the EQUAL rule is used with the 'address' of the consumer. Consider the following example of a single writer and three readers: The writer sets a key value of 0, 1 or 2 depending on which reader it the data is intended for. The reading method uses a key equal to its element number (element 0 requests with key 0 etc.). This allows the writer to route the data to whichever reader it requires. Stream Reordering
This example circuit implements a simple scalable processing farm which has a maximum concurrency of 'NP', where 'NP' is the number of CPU cores discovered at runtime. It has a thread that reads a stream of tasks from file. These are then processed in parallel and the results written to an output file in the same order that the tasks were read. In the general case, the time to process a task is non-deterministic (execution time depends on it's data) and so completion order is unknown. This example uses keys and rules to ensure that the input and output order are consistent. The following code is executed by the 'input' thread; TASK nextTask; The 'Proc' processing methods compete for tasks from the task stream and write their output using the task's associated key. Note that output needs to be collected before input otherwise a task could be accepted by a method which could then find that its output was full; resulting in deadlock. Int taskKey; Finally, the 'output' thread reads results using an incrementing key, which ensures that results are written to file in the same order that their corresponding tasks were read from file, regardless of the order that their processing completes in. Uns taskKey = 0; NotesWhen writing to stores/semaphores with keys, developers need to ensure that all possible key/rule combinations are covered by Readers. Any keyed buffers that are not read will ultimately fill the store. A generic catch all "No Rule" connection (akin to the C++ switch default case) should not be used as the "No Rule" connection could read the event first, stopping it going to the intended recipient. If a generic catch all-others option is required a non-overlapping "Greater Than/Less Than/Not_Equal" rule could be used. |